﻿/* ToDo: Eingrenzung der Log-Datei.
 *          Bisher wird einfach unendlich angehängt. 
 *        Erforderlich sind:
 *          Analysefunktion für Logdatei (Es kann nicht einfach hart ab einer 
 *          bestimmten Größe gecuttet werden da ein Logeintrag aus mehreren 
 *          Zeilen besteht. Denkbar wäre ein hartes cutten mit anschließendem
 *          backtracing zum Ende des vorhergehenden Eintrags.)
 *
 * ToDo: Logbehandlung in separatem Thread laufen lassen (BackGroundWorker)
 *          Die Frage ist zu klären ob alle aktiven Behandlungen in einem BW oder 
 *          in jeweils separaten BWs behandelt werden.
 */


using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using System.Reflection;


namespace CSharp.Libs.ExceptionHandling
{

    public class ExceptionCollector : IDisposable
    {

        #region global declarations

        // references
        Dictionary<Int32, cExceptionItem> m_ExceptionDictionary = null;
        StreamWriter m_SW = null;
        XmlWriterSettings m_XWSet = null;
        XmlWriter m_XW = null;
        ExceptionTools m_TOOLS = null;

        // variables
        private String m_strApplicationStartupPath = "";
        private String m_strXMLDebugFilePath = "";
        private String m_strXMLDebugFileName = "";
        private String m_strXMLDebugFileNameFullPath = "";
        private Int32 ExCount = 0;

        // mode switches
        Boolean m_fCollectExceptions = true;
        Boolean m_fRaiseTextEvent = true;
        Boolean m_fRaiseExceptionItemEvent = true;
        Boolean m_fWriteXMLDebugFile = true;

        // events
        public EventHandler<NewExceptionLogLineCreatedArgs> NewExceptionLogLineCreatedHandler;
        protected virtual void OnNewExceptionLogLineCreated(String Message)
        {
            EventHandler<NewExceptionLogLineCreatedArgs> LocalHandler = NewExceptionLogLineCreatedHandler;

            if (LocalHandler != null)
            {
                LocalHandler(this, new NewExceptionLogLineCreatedArgs(Message));
            }

        }
        #endregion


        #region eventhandling
        #region delegates
        public delegate void ExceptionItemEventHandler(object sender, ExceptionItemEventArgs e);
        #endregion

        #region events
        public event ExceptionItemEventHandler ExceptionItemEvent;
        #endregion

        #region raising subs
        protected virtual void RaiseExceptionEvent(ExceptionItemEventArgs eaE)
        {
            // Raise the event by using the () operator.
            ExceptionItemEvent(this, eaE);
        }
        #endregion
        #endregion


        #region Initialization / Termination
        /// <summary>
        ///     Constructor
        /// </summary>
        /// <remarks>
        ///     Initializes dictionaray object for collecting exceptions.
        ///     Gets the application startup path.
        ///     Defaults the XML debug file to the application startup path and sets its filename.
        ///     Initializes and opens global XML- and stream writer.
        /// </remarks>
        public ExceptionCollector()
        {
            try
            {
                //Initialize Tool class
                m_TOOLS = new ExceptionTools();

                //Initialize Dictionary
                m_ExceptionDictionary = new Dictionary<int, cExceptionItem>();
                
                //Set default paths
                m_strApplicationStartupPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
                m_strXMLDebugFilePath = m_strApplicationStartupPath;
                m_strXMLDebugFileName = "DebugFile.xml";
                InitializePath();

                //initialize XMLWriter settings object
                //InitializeXMLWriterSettings();

                //create stream objects
                InitializeStreams();
            }
            catch (Exception Ex)
            {
                HandleReceivedException(ref Ex, this.ToString(), "Constructor");
            }
        }

        /// <summary>
        ///     Terminator
        /// </summary>
        ~ExceptionCollector()
        {
        }
        #endregion


        #region IDisposable Member
        Boolean disposed = false; // used by IDisposable implementation
        public void Dispose()
        {
            Dispose(true);
            // This object will be cleaned up by the Dispose method.
            // Therefore, you should call GC.SupressFinalize to
            // take this object off the finalization queue
            // and prevent finalization code for this object
            // from executing a second time.
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called.
            if (!this.disposed)
            {
                // If disposing equals true, dispose all managed
                // and unmanaged resources.
                if (disposing)
                {
                    // Dispose managed resources.
                }

                // Call the appropriate methods to clean up
                // unmanaged resources here.
                // If disposing is false,
                // only the following code is executed.
                m_ExceptionDictionary.Clear();
                m_ExceptionDictionary = null;

                DestroyStreams();

                DestroyXMLWriterSettings();

                m_TOOLS = null;

                // Note disposing has been done.
                disposed = true;
            }
        }
        #endregion


        #region internal eventhandling
        private void HandleReceivedException(ref Exception Ex, String CallingClass, String CallingMethod)
        {
            try
            {
                //first off, create new instance of ExceptionItem class
                cExceptionItem ExItem = new cExceptionItem(Ex, CallingClass, CallingMethod);
                if (ExItem != null)
                {
                    /**********************************************************
                     *                                                        *
                     *              Main exception handling part              *
                     *                                                        *
                     *    all types of handling  are selectable via switch    *
                     *                                                        *
                     **********************************************************/
                    if (m_fCollectExceptions)
                    {   //add created instance to dictionary incrementing exception count
                        try
                        {
                            m_ExceptionDictionary.Add(ExCount++, ExItem);
                        }
                        catch(Exception ex)
                        {
                            cExceptionItem EI = new cExceptionItem(ex, this.ToString(), m_TOOLS.GetCurrentMethodName());
                            WriteXMLDebugFile(ref EI);
                        }
                    }
                    if (m_fRaiseTextEvent)
                    {   //fire an event containing a simple textline with the exception info
                        try
                        {
                            RaiseTextEvent(ref ExItem);
                        }
                        catch(Exception ex)
                        {
                            cExceptionItem EI = new cExceptionItem(ex, this.ToString(), m_TOOLS.GetCurrentMethodName());
                            WriteXMLDebugFile(ref EI);
                        }
                    }
                    if (m_fRaiseExceptionItemEvent)
                    {   //fire an event containing the complete exception item
                        try
                        {
                            RaiseExceptionEvent(new ExceptionItemEventArgs(ExItem));
                        }
                        catch(Exception ex)
                        {
                            cExceptionItem EI = new cExceptionItem(ex, this.ToString(), m_TOOLS.GetCurrentMethodName());
                            WriteXMLDebugFile(ref EI);
                        }
                    }
                    if (m_fWriteXMLDebugFile)
                    {   //write exception info to a xml file
                        try
                        {
                            WriteXMLDebugFile(ref ExItem);
                        }
                        catch
                        {
                        }
                    }
                }
            }
            catch (Exception LocalEx)
            {
                cExceptionItem EI = new cExceptionItem(LocalEx, this.ToString(), m_TOOLS.GetCurrentMethodName());
                WriteXMLDebugFile(ref EI);
            }
        }
        #endregion


        #region private functions
        #region     utilizing functions
        private void InitializePath()
        {
            m_strXMLDebugFileNameFullPath = m_strXMLDebugFilePath + "\\" + m_strXMLDebugFileName;
        }

        private void InitializeXMLWriterSettings()
        {
            try
            {
                //kill if exists
                DestroyXMLWriterSettings();

                //instanciate settings object
                m_XWSet = new XmlWriterSettings();
                m_XWSet.ConformanceLevel = ConformanceLevel.Fragment;//.Auto;
                m_XWSet.NewLineChars = Environment.NewLine;
                m_XWSet.NewLineOnAttributes = true;
                m_XWSet.IndentChars = "\t";
                m_XWSet.Indent = true;
            }
            catch (Exception Ex)
            {
                HandleReceivedException(ref Ex, this.ToString(), get_current_method_name());
            }
        }

        private void DestroyXMLWriterSettings()
        {
            if (m_XWSet != null)
            {
                m_XWSet = null;
            }
        }

        private void InitializeStreams()
        {
            try
            {
                //kill streams if exist
                if (m_XW != null || m_SW != null)
                {
                    DestroyStreams();
                }

                //create XMLWriterSettings object if doesn't exist
                if (m_XWSet == null)
                {
                    InitializeXMLWriterSettings();
                }

                //initialize streams
                m_SW = new StreamWriter(m_strXMLDebugFileNameFullPath, true);
                m_XW = XmlWriter.Create(m_SW, m_XWSet);
            }
            catch (Exception Ex)
            {
                HandleReceivedException(ref Ex, this.ToString(), get_current_method_name());
            }
        }

        private void DestroyStreams()
        {
            try
            {
                if (m_XW != null)
                {
                    //Destroy XMLWriter
                    if (m_XW.WriteState != WriteState.Start
                    &&  m_XW.WriteState != WriteState.Error) 
                    {
                        m_XW.Flush();
                    }
                    m_XW.Close();
                    m_XW = null;
                }

            }
            catch 
            {
                //HandleReceivedException(ref Ex, this.ToString(), get_current_method_name());
            }
            finally
            {
                m_XW = null;

                if (m_SW != null &&
                    m_SW.BaseStream.CanWrite)
                {
                    //Append last NewLine
                    m_SW.Write(Environment.NewLine);
                    m_SW.Flush();
                    //m_SW.Close();
                    //m_SW.Dispose();
                    m_SW = null;
                }
            }
        }

        private string get_current_method_name()
        {
            System.Diagnostics.StackFrame stackframe =
                new System.Diagnostics.StackFrame(1, true);

            return stackframe.GetMethod().ToString();
        }
        #endregion

        #region     functional functions        
        private void RaiseTextEvent(ref cExceptionItem ExItem)
        {
            try
            {
                String strLogLine = ExItem.DateTime.ToString() + " " +
                    ExItem.CallingClass + " " +
                    ExItem.CallingMethod + " " +
                    ExItem.Ex.Message + Environment.NewLine;
                OnNewExceptionLogLineCreated(strLogLine);
            }
            catch (Exception Ex)
            {
                HandleReceivedException(ref Ex, this.ToString(), get_current_method_name());
            }
        }

        private void WriteXMLDebugFile(ref cExceptionItem ExItem)
        {
            try
            {
                if (m_XW != null &&
                    m_XW.WriteState != WriteState.Error)
                {
                    m_XW.WriteStartElement(ExItem.Ex.GetType().ToString());
                    m_XW.WriteElementString("DateTime", ExItem.DateTime.ToString());
                    m_XW.WriteElementString("CallingClass", ExItem.CallingClass);
                    m_XW.WriteElementString("CallingMethod", ExItem.CallingMethod);
                    m_XW.WriteElementString("Message", ExItem.Ex.Message);
                    m_XW.WriteFullEndElement();
                    //m_XW.WriteRaw(Environment.NewLine);
                    m_XW.Flush();
                }
            }
            catch (Exception Ex)
            {
                HandleReceivedException(ref Ex, this.ToString(), get_current_method_name());
            }
        }
        #endregion
        #endregion


        #region interface
        #region     properties
        public ExceptionTools Tools
        {
            get { return m_TOOLS; }
        }

        public Boolean DoCollectExceptions
        {
            get { return m_fCollectExceptions; }
            set { m_fCollectExceptions = value; }
        }
        
        public Boolean DoRaiseTextEvents
        {
            get { return m_fRaiseTextEvent; }
            set { m_fRaiseTextEvent = value; }
        }

        public Boolean DoRaiseExceptionItemEvents
        {
            get { return m_fRaiseExceptionItemEvent; }
            set { m_fRaiseExceptionItemEvent = value; }
        }

        public Boolean DoWriteXMLDebugFile
        {
            get { return m_fWriteXMLDebugFile; }
            set { m_fWriteXMLDebugFile = value; }
        }


        public Dictionary<Int32, cExceptionItem> ExceptionDictionary
        {
            get { return m_ExceptionDictionary; }
        }

        public String XMLDebugFilePath
        {
          	get { return m_strXMLDebugFilePath; }
            set
            {
              	if (Directory.Exists(value))
                {
                    //Refresh Path and Streams
                    m_strXMLDebugFilePath = value;
                    InitializePath();
                    InitializeStreams();
                }
            }
        }
        
        public String XMLDebugFileName
        {
            get { return m_strXMLDebugFileName; }
            set 
            {
                //Refresh Path and Streams
                m_strXMLDebugFileName = value;
                InitializePath();
                InitializeStreams();
            }
        }

        public String XMLDebugFileNameFullPath
        {
            get { return m_strXMLDebugFileNameFullPath; }
        }
        #endregion

        #region     methods
        public void AddException(Exception Ex, String CallingClass, String CallingMethod)
        {
            //handle exception (i.e. write debug file or raise events with 
            //  text of newly added exception)
            HandleReceivedException(ref Ex, CallingClass, CallingMethod);
        }
        #endregion
        #endregion

    }



    /// <summary>
    /// 
    /// </summary>
    public class cExceptionItem
    {
	    private DateTime _DateTime = DateTime.Now;
	    private string _CallingClass = "";
	    private string _CallingMethod = "";
	    private Exception _Ex = null;
	    
        public System.DateTime DateTime 
        {
		    get 
            { 
                return _DateTime; 
            }
	    }
	    
        public string CallingClass 
        {
		    get 
            { 
                return _CallingClass; 
            }
	    }
	    
        public string CallingMethod 
        {
		    get 
            { 
                return _CallingMethod; 
            }
	    }

	    public Exception Ex 
        {
		    get 
            { 
                return _Ex; 
            }
	    }
	    
        public cExceptionItem(Exception Ex, string CallingClass, string CallingMethod)
	    {
		    _DateTime = DateTime.Now;
		    _CallingClass = CallingClass;
		    _CallingMethod = CallingMethod;
		    _Ex = Ex;
	    }

    }



    public class NewExceptionLogLineCreatedArgs : EventArgs
    {
        public NewExceptionLogLineCreatedArgs(string s) 
        { 
            Text = s; 
        }

        public String Text { get; private set; } // readonly
    }



    public class ExceptionItemEventArgs : EventArgs
    {
        cExceptionItem m_EI = null;

        public ExceptionItemEventArgs(cExceptionItem EI)
        {
            m_EI = EI;
        }

        public cExceptionItem ExceptionItem 
        { 
            get
            {
                return m_EI;
            } 
        }//readonly
    }



}


